<?php
/**
 * Project: AIGC
 * Author: moufer <moufer@163.com>
 * Date: 2024/10/13 21:22
 */

namespace module\ai\library\OpenAI;

use OpenAI\Contracts\Resources\ChatContract;
use OpenAI\Contracts\TransporterContract;
use OpenAI\Responses\Chat\CreateResponse;
use OpenAI\Responses\StreamResponse;
use OpenAI\ValueObjects\Transporter\Payload;
use OpenAI\ValueObjects\Transporter\Response;


class Chat implements ChatContract
{
    use \OpenAI\Resources\Concerns\Streamable;
    use \OpenAI\Resources\Concerns\Transportable;

    protected string    $apiPath           = 'chat/completions';
    protected ?\Closure $formatDataHandler = null;

    public function withApiPath(string $apiPath): self
    {
        $this->apiPath = $apiPath;

        return $this;
    }

    public function withFormatData(\Closure $function): static
    {
        $this->formatDataHandler = $function;
        return $this;
    }

    /**
     * Creates a completion for the chat message
     *
     * @see https://platform.openai.com/docs/api-reference/chat/create
     *
     * @param array<string, mixed> $parameters
     */
    public function create(array $parameters): CreateResponse
    {
        $this->ensureNotStreamed($parameters);

        $payload = Payload::create($this->apiPath, $parameters);

        /** @var Response<array{id: string, object: string, created: int, model: string, system_fingerprint?: string, choices: array<int, array{index: int, message: array{role: string, content: ?string, function_call: ?array{name: string, arguments: string}, tool_calls: ?array<int, array{id: string, type: string, function: array{name: string, arguments: string}}>}, finish_reason: string|null}>, usage: array{prompt_tokens: int, completion_tokens: int|null, total_tokens: int}}> $response */
        $response = $this->transporter->requestObject($payload);

        $data = $this->formatData($response->data());

        return CreateResponse::from($data, $response->meta());
    }

    /**
     * Creates a streamed completion for the chat message
     *
     * @see https://platform.openai.com/docs/api-reference/chat/create
     *
     * @param array<string, mixed> $parameters
     * @return StreamResponse<CreateStreamedResponse>
     */
    public function createStreamed(array $parameters): StreamResponse
    {
        $parameters = $this->setStreamParameter($parameters);

        $payload = Payload::create($this->apiPath, $parameters);

        $response = $this->transporter->requestStream($payload);

        CreateStreamedResponse::formatterHandler(fn($data) => $this->formatData($data));

        return new StreamResponse(CreateStreamedResponse::class, $response);
    }

    public function getTransporter(): TransporterContract
    {
        return $this->transporter;
    }

    protected function formatData(array $data): array
    {
        if ($this->formatDataHandler) {
            return ($this->formatDataHandler)($data);
        }

        $data['id'] = $data['id'] ?? $data['sid'] ?? '';
        $data['object'] = $data['object'] ?? 'chat.completion';
        $data['created'] = $data['created'] ?? time();
        $data['model'] = $data['model'] ?? '';

        return $data;
    }

}